package org.andengine.engine.camera.hud.controls;

import android.view.MotionEvent;

import org.andengine.engine.camera.Camera;
import org.andengine.engine.camera.hud.HUD;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;
import org.andengine.entity.scene.IOnSceneTouchListener;
import org.andengine.entity.scene.Scene;
import org.andengine.entity.sprite.Sprite;
import org.andengine.input.touch.TouchEvent;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.vbo.VertexBufferObjectManager;
import org.andengine.util.math.MathUtils;

import static org.andengine.util.Constants.VERTEX_INDEX_X;
import static org.andengine.util.Constants.VERTEX_INDEX_Y;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 10:43:09 - 11.07.2010
 */
public abstract class BaseOnScreenControl extends HUD implements IOnSceneTouchListener {
    // ===========================================================
    // Constants
    // ===========================================================

    private static final int INVALID_POINTER_ID = -1;

    // ===========================================================
    // Fields
    // ===========================================================

    private final Sprite mControlBase;
    private final Sprite mControlKnob;

    private float mControlValueX;
    private float mControlValueY;

    private final IOnScreenControlListener mOnScreenControlListener;

    private int mActivePointerID = INVALID_POINTER_ID;

    // ===========================================================
    // Constructors
    // ===========================================================

    public BaseOnScreenControl(final float pX, final float pY, final Camera pCamera, final ITextureRegion pControlBaseTextureRegion, final ITextureRegion pControlKnobTextureRegion, final float pTimeBetweenUpdates, final VertexBufferObjectManager pVertexBufferObjectManager, final IOnScreenControlListener pOnScreenControlListener) {
        this.setCamera(pCamera);

        this.mOnScreenControlListener = pOnScreenControlListener;
        /* Create the control base. */
        this.mControlBase = new Sprite(pX, pY, pControlBaseTextureRegion, pVertexBufferObjectManager) {
            @Override
            public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
                return BaseOnScreenControl.this.onHandleControlBaseTouched(pSceneTouchEvent, pTouchAreaLocalX, pTouchAreaLocalY);
            }
        };

		/* Create the control knob. */
        this.mControlKnob = new Sprite(0, 0, pControlKnobTextureRegion, pVertexBufferObjectManager);
        this.onHandleControlKnobReleased();

		/* Register listeners and add objects to this HUD. */
        this.setOnSceneTouchListener(this);
        this.registerTouchArea(this.mControlBase);
        this.registerUpdateHandler(new TimerHandler(pTimeBetweenUpdates, true, new ITimerCallback() {
            @Override
            public void onTimePassed(final TimerHandler pTimerHandler) {
                BaseOnScreenControl.this.mOnScreenControlListener.onControlChange(BaseOnScreenControl.this, BaseOnScreenControl.this.mControlValueX, BaseOnScreenControl.this.mControlValueY);
            }
        }));

        this.attachChild(this.mControlBase);
        this.attachChild(this.mControlKnob);

        this.setTouchAreaBindingOnActionDownEnabled(true);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public Sprite getControlBase() {
        return this.mControlBase;
    }

    public Sprite getControlKnob() {
        return this.mControlKnob;
    }

    public IOnScreenControlListener getOnScreenControlListener() {
        return this.mOnScreenControlListener;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent pSceneTouchEvent) {
        final int pointerID = pSceneTouchEvent.getPointerID();
        if (pointerID == this.mActivePointerID) {
            this.onHandleControlBaseLeft();

            switch (pSceneTouchEvent.getAction()) {
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    this.mActivePointerID = INVALID_POINTER_ID;
            }
        }
        return false;
    }

    // ===========================================================
    // Methods
    // ===========================================================

    public void refreshControlKnobPosition() {
        this.onUpdateControlKnob(this.mControlValueX * 0.5f, this.mControlValueY * 0.5f);
    }

    /**
     * When the touch happened outside of the bounds of this OnScreenControl.
     */
    protected void onHandleControlBaseLeft() {
        this.onUpdateControlKnob(0, 0);
    }

    /**
     * When the OnScreenControl was released.
     */
    protected void onHandleControlKnobReleased() {
        this.onUpdateControlKnob(0, 0);
    }

    protected boolean onHandleControlBaseTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
        final int pointerID = pSceneTouchEvent.getPointerID();

        switch (pSceneTouchEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (this.mActivePointerID == INVALID_POINTER_ID) {
                    this.mActivePointerID = pointerID;
                    this.updateControlKnob(pTouchAreaLocalX, pTouchAreaLocalY);
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (this.mActivePointerID == pointerID) {
                    this.mActivePointerID = INVALID_POINTER_ID;
                    this.onHandleControlKnobReleased();
                    return true;
                }
                break;
            default:
                if (this.mActivePointerID == pointerID) {
                    this.updateControlKnob(pTouchAreaLocalX, pTouchAreaLocalY);
                    return true;
                }
                break;
        }
        return true;
    }

    private void updateControlKnob(final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
        final Sprite controlBase = this.mControlBase;

        final float relativeX = MathUtils.bringToBounds(0, controlBase.getWidth(), pTouchAreaLocalX) / controlBase.getWidth() - 0.5f;
        final float relativeY = MathUtils.bringToBounds(0, controlBase.getHeight(), pTouchAreaLocalY) / controlBase.getHeight() - 0.5f;

        this.onUpdateControlKnob(relativeX, relativeY);
    }

    /**
     * @param pRelativeX from <code>-0.5</code> (left) to <code>0.5</code> (right).
     * @param pRelativeY from <code>-0.5</code> (top) to <code>0.5</code> (bottom).
     */
    protected void onUpdateControlKnob(final float pRelativeX, final float pRelativeY) {
        final Sprite controlBase = this.mControlBase;
        final Sprite controlKnob = this.mControlKnob;

        this.mControlValueX = 2 * pRelativeX;
        this.mControlValueY = 2 * pRelativeY;

        final float[] controlBaseSceneCenterCoordinates = controlBase.getSceneCenterCoordinates();
        final float x = controlBaseSceneCenterCoordinates[VERTEX_INDEX_X] - controlKnob.getWidth() * 0.5f + pRelativeX * controlBase.getWidthScaled();
        final float y = controlBaseSceneCenterCoordinates[VERTEX_INDEX_Y] - controlKnob.getHeight() * 0.5f + pRelativeY * controlBase.getHeightScaled();

        controlKnob.setPosition(x, y);
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static interface IOnScreenControlListener {
        // ===========================================================
        // Constants
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        /**
         * @param pBaseOnScreenControl
         * @param pValueX              between <code>-1</code> (left) to <code>1</code> (right).
         * @param pValueY              between <code>-1</code> (up) to <code>1</code> (down).
         */
        public void onControlChange(final BaseOnScreenControl pBaseOnScreenControl, final float pValueX, final float pValueY);
    }
}
